package instructionSelection.jouette;

import Assem.Instr;
import Assem.InstrList;
import translate.activationRegister.Frame;
import translate.activationRegister.temp.Label;
import translate.activationRegister.temp.Temp;
import translate.activationRegister.temp.TempList;
import translate.activationRegister.temp.LabelList;
import treeIR.BINOP;
import treeIR.CALL;
import treeIR.CJUMP;
import treeIR.CONST;
import treeIR.Exp;
import treeIR.ExpList;
import treeIR.JUMP;
import treeIR.LABEL;
import treeIR.MEM;
import treeIR.MOVE;
import treeIR.NAME;
import treeIR.SEQ;
import treeIR.Stm;
import treeIR.TEMP;

/**
 * 
 * 
 * */
public class Codegen {
	
	/***/
	private Frame frame;
	/***/
	private InstrList ilist = null, last = null;
	
	
	/**
	 * 
	 * */
	public Codegen(Frame f){
		frame = f;
	}
	
	/**
	 * 
	 * */
	private void emit (Instr inst){
		if(last!=null)
			last = (InstrList) (last.tail = new InstrList(inst, null));
		else last = ilist = new InstrList(inst, null);
	}
	
	/**
	 * 
	 * */
	private void munchStm(Stm s){
		if(s instanceof MOVE) munchMove((MOVE)s);
		else if(s instanceof SEQ) munchSeq((SEQ)s);
		else if(s instanceof LABEL) munchLabel((LABEL)s);
		else if(s instanceof JUMP) munchJump((JUMP) s);
		else if(s instanceof CJUMP) munchCJump((CJUMP) s);
	}
	
	/**
	 * 
	 * */
	private TempList L(Temp h, TempList t) {
		return new TempList(h,t);
	}
	
	/**
	 * 
	 * */
	private void munchMove(MOVE m){
		if(m.dst instanceof MEM){
			MEM mem1 = (MEM)m.dst;
			Exp e2 = m.src;			
			if(mem1.exp instanceof BINOP){
				BINOP binop = (BINOP) mem1.exp;
				if (binop.right instanceof CONST){
					CONST constant = (CONST)binop.right;
					Temp t1, t2;
					t1 = munchExp(binop.left);
					t2 = munchExp(e2);
					emit( new Assem.OPER("STORE M[ `d0  + "+constant.value+"] <- `s0\n" , L(t1,null), L(t2, null), null));
				}
				else if(binop.left instanceof CONST){
					CONST constant = (CONST)binop.left;
					Temp t1, t2;
					t1 = munchExp(binop.right);
					t2 = munchExp(e2);
					emit( new Assem.OPER("STORE M[ `d0 + " +constant.value+"] <- `s0\n" , L(t1,null), L(t2, null), null));
				}//fim else..if
			}
			else if(e2 instanceof MEM){
				MEM mem2 = (MEM) e2;
				Temp t1, t2;
				t1 = munchExp(mem1.exp);
				t2 = munchExp(mem2.exp);
				emit(new Assem.MOVE("MOVEM M[`d0] <- M[`s0]\n", t1, t2));
			}
			else if(mem1.exp instanceof CONST){
				CONST constant = (CONST) mem1.exp;
				Temp t1;
				t1 = munchExp(e2);
				emit(new Assem.OPER("STORE M[r0 + "+constant.value+"] <- `s0\n", null, L(t1,null), null));
			}
			else{
				Temp t1, t2;
				t1 = munchExp(mem1.exp);
				t2 = munchExp(e2);
				emit(new Assem.OPER("STORE M[`d0] <- `s0\n", L(t1,null), L(t2,null), null));
			}//fim if..else
		}else if(m.dst instanceof TEMP){
			TEMP temp = (TEMP)m.dst;
			Temp t1;
			t1 = munchExp(m.src);
			emit(new Assem.OPER("ADD `d0 <- r0 + `s0\n", L(temp.temp, null), L(t1, null), null));
		}
	}//fim metodo MunchMove
	
	/**
	 * 
	 * */
	private void munchSeq(SEQ s){
		munchStm(s.left);
		munchStm(s.right);
	}

	/**
	 * 
	 * */
	private void munchLabel(LABEL l){
		emit(new Assem.LABEL(l.label.toString() + ":\n", l.label));
	}
	
	/***/
	private Temp munchExp(Exp e){
		if(e instanceof MEM){
			MEM mem1 = (MEM)e;
			if(mem1.exp instanceof BINOP){
				BINOP binop = (BINOP) mem1.exp;
				if (binop.right instanceof CONST){
					Temp r = new Temp();
					CONST constant = (CONST)binop.right;
					Temp t1 = munchExp(binop.left);
					emit(new Assem.OPER("LOAD `d0 <- M[`s0 + "+constant.value+"]\n", L(r,null), L(t1,null), null));
					return r;
				}
				else if(binop.left instanceof CONST){
					Temp r = new Temp();
					CONST constant = (CONST)binop.left;
					Temp t1 = munchExp(binop.right);
					emit(new Assem.OPER("LOAD `d0 <- M[ `s0 +"+constant.value+"]\n", L(r,null), L(t1,null), null));
					return r;
				}//fim if..else
			}
			else if(mem1.exp instanceof CONST){
				Temp r = new Temp();
				CONST constant = (CONST)mem1.exp;
				emit(new Assem.OPER("LOAD `d0 <- M[r0 +"+constant.value+"]\n", L(r,null), null, null));
				return r;
			}
		}
		else if(e instanceof MEM){
			Temp r = new Temp();
			MEM mem = (MEM)e;
			Temp t1 = munchExp(mem.exp);
			emit(new Assem.OPER("LOAD `d0 <- M[`s0 + r0]\n", L(r,null), L(t1,null), null));

			return r;
		}
		else if(e instanceof BINOP){
			BINOP binop = (BINOP)e;
			if(binop.right instanceof CONST){
				Temp r = new Temp();
				CONST constant = (CONST)binop.right;
				Temp t1 = munchExp(binop.left);
				
				switch(binop.binop){
				case BINOP.PLUS:
					emit(new Assem.OPER("ADDI `d0 <- `s0 + "+constant.value+"\n", L(r,null), L(t1,null), null));
					break;
				case BINOP.MINUS:
					emit(new Assem.OPER("SUBI `d0 <- `s0 - "+constant.value+"\n", L(r,null), L(t1,null), null));
					break;
				case BINOP.MUL:
					emit(new Assem.OPER("MUL `d0 <- `s0 * "+constant.value+"\n", L(r,null), L(t1, null), null));
					break;
				case BINOP.DIV:
					emit(new Assem.OPER("DIV `d0 <- `s0 / "+constant.value+"\n", L(r,null), L(t1,null), null));
					break;
				}
				return r;
			}
			else if(binop.left instanceof CONST){
				Temp r = new Temp();
				CONST constant = (CONST)binop.left;
				Temp t1 = munchExp(binop.right);
				switch(binop.binop){
				case BINOP.PLUS:
					emit(new Assem.OPER("ADDI `d0 <- `s0 + "+constant.value+"\n", L(r,null), L(t1,null), null));
					break;
				case BINOP.MINUS:
					emit(new Assem.OPER("SUBI `d0 <- `s0 - "+constant.value+"\n", L(r,null), L(t1,null), null));
					break;
				case BINOP.MUL:
					emit(new Assem.OPER("MUL `d0 <- `s0 * "+constant.value+"\n", L(r,null), L(t1, null), null));
					break;
				case BINOP.DIV:
					emit(new Assem.OPER("DIV `d0 <- `s0 / "+constant.value+"\n", L(r,null), L(t1,null), null));
					break;
				}
				return r;
			}
			else{
				Temp r = new Temp();
				Temp t1,t2;
				t1 = munchExp(binop.left);
				t2 = munchExp(binop.right);
				
				switch(binop.binop){
				case BINOP.PLUS:
					emit(new Assem.OPER("ADD `d0 <- `s0  + `s1\n", L(r,null), L(t1, L(t2,null)), null));
					break;
				case BINOP.MINUS:
					emit(new Assem.OPER("SUB `d0 <- `s0 - `s1\n", L(r,null), L(t1, L(t2,null)), null));
					break;
				case BINOP.MUL:
					emit(new Assem.OPER("MUL `d0 <- `s0 * `s1\n", L(r,null), L(t1, L(t2,null)), null));
					break;
				case BINOP.DIV:
					emit(new Assem.OPER("DIV `d0 <- `s0 / `s1\n", L(r,null), L(t1, L(t2,null)), null));
					break;
				}
				
				return r;
			}
		}
		else if(e instanceof CONST){
			Temp r = new Temp();
			CONST constant = (CONST)e;
			emit(new Assem.OPER("ADDI `d0 <- r0 + "+constant.value+"\n", L(r,null), null, null));
			return r;
		}
		else if(e instanceof TEMP){
			TEMP temp = (TEMP)e;
			return temp.temp;
		}
		else if(e instanceof CALL){
			CALL c = (CALL)e;
			Temp r = new Temp();
			Temp t1 = munchExp(c.func);
			TempList tempList = null;
			Exp head = c.args.head;
			ExpList tail = (ExpList) c.args.tail; 
			while (head!=null){
				tempList = new TempList(munchExp(head), tempList);
				if(tail!=null) {
					head = tail.head;
					tail = (ExpList) tail.tail;
				}
				else head=null;
			}			
			//revertendo lista
			TempList auxList = null;
			Temp headTemp = tempList.head;
			TempList tailTemp = (TempList) tempList.tail;		
			while (headTemp!=null){
				
				auxList = new TempList(headTemp, auxList);
				
				if(tailTemp!=null) {
					headTemp = tailTemp.head;
					tailTemp = (TempList) tailTemp.tail;
				}
				else headTemp=null;
			}
			tempList = auxList;
			emit(new Assem.OPER("CALL `s0\n", L(frame.RV(),null), L(t1 ,tempList), null));
			return r;
		}
		return null;
	}
	
	/**
	 * 
	 * */
	private void munchJump(JUMP j){
		Temp t1;
		NAME name = (NAME) j.exp;
		t1 = new Temp();
		emit(new Assem.OPER("JUMP goto `s0\n", null, L(t1,null), new LabelList(name.label, null)));
	}
	
	/**
	 * 
	 * */
	private void munchCJump(CJUMP cj){
		Temp left = munchExp(cj.left);
		Temp right = munchExp(cj.right);
		Temp r = new Temp();
		Label lTrue = cj.iftrue;
		Label lFalse = cj.iffalse;
		Temp t1 = new Temp();
		switch(cj.relop){
		case CJUMP.GE:
			emit(new Assem.OPER("SUB `d0 <- `s0 - `s1\n", L(r, null), L(left, L(right,null)), null));
			emit(new Assem.OPER("BRANCHGE if `s0 >= 0 goto "+lTrue+"\n", null, L(r, null), new LabelList(lTrue, null)));
			emit(new Assem.OPER("JUMP goto `s0\n", null, L(t1,null), new LabelList(lFalse, null)));
			break;
		case CJUMP.LT:
			emit(new Assem.OPER("SUB `d0 <- `s0 - `s1\n", L(r, null), L(left, L(right,null)), null));
			emit(new Assem.OPER("BRANCHLT if `s0 < 0 goto "+lTrue+"\n", null, L(r, null), new LabelList(lTrue, null)));
			emit(new Assem.OPER("JUMP goto `s0\n", null, L(t1,null), new LabelList(lFalse, null)));
			break;
		case CJUMP.EQ:
			emit(new Assem.OPER("SUB `d0 <- `s0 - `s1\n", L(r, null), L(left, L(right,null)), null));
			emit(new Assem.OPER("BRANCHEQ if `s0 == 0 goto "+lTrue+"\n", null, L(r, null), new LabelList(lTrue, null)));
			emit(new Assem.OPER("JUMP goto `s0\n", null, L(t1,null), new LabelList(lFalse, null)));
			break;
		case CJUMP.NE:
			emit(new Assem.OPER("SUB `d0 <- `s0 - `s1\n", L(r, null), L(left, L(right,null)), null));
			emit(new Assem.OPER("BRANCHNE if `s0 != 0 goto "+lTrue+"\n", null, L(r, null), new LabelList(lTrue, null)));
			emit(new Assem.OPER("JUMP goto `s0\n", null, L(t1,null), new LabelList(lFalse, null)));
		}//fim switch
	}
	
	//metodo principal da classe que eh utilizado pelo Frame
	/**
	 * 
	 * 
	 * */
	public InstrList codegen(Stm s){
		InstrList l;
		munchStm(s);
		l=ilist;
		ilist=last=null;
		return l;
	}
}//fim classe

